package com.demister.at.model;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

/**
 *
 * @author Rock
 */
public class RemoveChainCommand extends BlockCommand {

    private List<Block> srcBlock = new LinkedList<Block>();
    private LinkedList<Block> queue = new LinkedList<Block>();
    private List<Block> removedBlock = new LinkedList<Block>();
    private ArrayList<Integer> levelCnt = new ArrayList<Integer>();

    public RemoveChainCommand(BlockFrame frame, BlockTypeSchema schema) {
        super(frame, schema);
    }

    /**
     * Remove the blocks.
     */
    @Override
    public void excute() {
        int currentLevelCounter = 0;
        int nextLevelCounter = 0;
        removedBlock.clear();
        levelCnt.clear();
        queue.clear();
        Block b1 = null;
        ListIterator<Block> itr = srcBlock.listIterator();

        while (itr.hasNext()) {
            Block b = itr.next();
            boolean removed = false;
            if (!frame.isAtTop(b)) {
                for (int i = b.getX(); i <= b.getBottomRightPoint().getX();) {
                    b1 = frame.get(i, b.getY() - 1);
                    if (b1 != null) {
                        if (schema.isRemovableSameType(b.getType(), b1.getType())) {
                            removed = true;
                            if (!schema.isRemoveType(b1.getType())) {
                                frame.remove(b1);
                                queue.add(b1);
                                nextLevelCounter++;
                            }
                        }
                        i = b1.getBottomRightPoint().getX() + 1;
                    } else {
                        i++;
                    }
                }
            }

            if (!frame.isAtRightBound(b)) {
                for (int i = b.getY(); i <= b.getBottomRightPoint().getY();) {
                    b1 = frame.get(b.getBottomRightPoint().getX() + 1, i);
                    if (b1 != null) {
                        if (schema.isRemovableSameType(b.getType(), b1.getType())) {
                            removed = true;
                            if (!schema.isRemoveType(b1.getType())) {
                                frame.remove(b1);
                                queue.add(b1);
                                nextLevelCounter++;
                            }
                        }
                        i = b1.getBottomRightPoint().getY() + 1;
                    } else {
                        i++;
                    }
                }
            }

            if (!frame.isAtBottom(b)) {
                for (int i = b.getX(); i <= b.getBottomRightPoint().getX();) {
                    b1 = frame.get(i, b.getBottomRightPoint().getY() + 1);
                    if (b1 != null) {
                        if (schema.isRemovableSameType(b.getType(), b1.getType())) {
                            removed = true;
                            if (!schema.isRemoveType(b1.getType())) {
                                frame.remove(b1);
                                queue.add(b1);
                                nextLevelCounter++;
                            }
                        }
                        i = b1.getBottomRightPoint().getX() + 1;
                    } else {
                        i++;
                    }
                }
            }

            if (!frame.isAtLeftBound(b)) {
                for (int i = b.getY(); i <= b.getBottomRightPoint().getY(); i++) {
                    b1 = frame.get(b.getX() - 1, i);
                    if (b1 != null) {
                        if (schema.isRemovableSameType(b.getType(), b1.getType())) {
                            removed = true;
                            if (!schema.isRemoveType(b1.getType())) {
                                frame.remove(b1);
                                queue.add(b1);
                                nextLevelCounter++;
                            }
                        }
                        i = b1.getBottomRightPoint().getY() + 1;
                    } else {
                        i++;
                    }
                }
            }

            if (removed) {
                removedBlock.add(b);
                itr.remove();
            }
        }

        if (removedBlock.isEmpty()) {
            return;
        }

        currentLevelCounter = removedBlock.size();
        for (Block block : removedBlock) {
            frame.remove(block);
        }

        levelCnt.add(currentLevelCounter);

        Block b = null;
        while (true) {
            if (currentLevelCounter == 0) {
                if (nextLevelCounter != 0) {
                    this.levelCnt.add(nextLevelCounter);
                    currentLevelCounter = nextLevelCounter;
                    nextLevelCounter = 0;
                } else {
                    break;
                }
            }

            b = queue.poll();
            removedBlock.add(b);
            currentLevelCounter--;

            if (!frame.isAtTop(b)) {
                for (int i = b.getX(); i <= b.getBottomRightPoint().getX();) {
                    b1 = frame.get(i, b.getY() - 1);
                    if (b1 != null) {
                        if (schema.isRemovableSameType(b.getType(), b1.getType())) {
                            frame.remove(b1);
                            queue.add(b1);
                            nextLevelCounter++;
                        }
                        i = b1.getBottomRightPoint().getX() + 1;

                    } else {
                        i++;
                    }
                }
            }

            if (!frame.isAtRightBound(b)) {
                for (int i = b.getY(); i <= b.getBottomRightPoint().getY();) {
                    b1 = frame.get(b.getBottomRightPoint().getX() + 1, i);
                    if (b1 != null) {
                        if (schema.isRemovableSameType(b.getType(), b1.getType())) {
                            frame.remove(b1);
                            queue.add(b1);
                            nextLevelCounter++;
                        }
                        i = b1.getBottomRightPoint().getY() + 1;

                    } else {
                        i++;
                    }
                }
            }

            if (!frame.isAtBottom(b)) {
                for (int i = b.getX(); i <= b.getBottomRightPoint().getX();) {
                    b1 = frame.get(i, b.getBottomRightPoint().getY() + 1);
                    if (b1 != null) {
                        if (schema.isRemovableSameType(b.getType(), b1.getType())) {
                            frame.remove(b1);
                            queue.add(b1);
                            nextLevelCounter++;
                        }
                        i = b1.getBottomRightPoint().getX() + 1;

                    } else {
                        i++;
                    }
                }
            }

            if (!frame.isAtLeftBound(b)) {
                for (int i = b.getY(); i <= b.getBottomRightPoint().getY(); i++) {
                    b1 = frame.get(b.getX() - 1, i);
                    if (b1 != null) {
                        if (schema.isRemovableSameType(b.getType(), b1.getType())) {
                            frame.remove(b1);
                            queue.add(b1);
                            nextLevelCounter++;
                        }
                        i = b1.getBottomRightPoint().getY() + 1;
                    } else {
                        i++;
                    }
                }
            }
        }
    }

    /**
     * Add block into the command as source.
     * If the block is not remove type, nothing would happen.
     * @param b the adding block
     */
    public void add(Block b) {
        if (schema.isRemoveType(b.getType())) {
            srcBlock.add(b);
        }
    }

    /**
     * Add block as source. No remove type checking.
     * @param b
     */
    public void addRemoveSourceBlock(Block b) {
        srcBlock.add(b);
    }

    /**
     * Return the removed blocks.
     * @return the removed blocks
     */
    public List<Block> getRemovedBlock() {
        return this.removedBlock;
    }

    /**
     * Return the level count.
     * @return the level count
     */
    public List<Integer> getLevelCount() {
        return this.levelCnt;
    }
}
